#include <stdarg.h>
#include <time.h>
#include <dlfcn.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include "cloging.h"

static int __clog_write_init(int prio, const char* tag, const char* log_buf);
#ifndef CLOG_LOG_WRITE_P
#define CLOG_LOG_WRITE_P __clog_write_init
#endif
clog_global_state_t g_clog_global_state = {
    .clog_log_write_p = CLOG_LOG_WRITE_P,
    .init_once = PTHREAD_ONCE_INIT,
    .consumer_thread = 0,
    .thread_running = 0,
};

static int get_loglevel(void)
{
	char *loglevel_env = NULL;
	int loglevel_value = CLOG_ERROR;

	loglevel_env = getenv(LOGLEVEL_ENV);
	if (loglevel_env != NULL) {
		loglevel_value = atoi(loglevel_env);
		/* loglevel value should in the configuration area */
		if ((loglevel_value >= CLOG_UNKNOWN) && (loglevel_value < CLOG_MAX)) {
            return loglevel_value;
        }
			
	}
	/* default log level */
	loglevel_value = CLOG_ERROR;

	return loglevel_value;
}

static char get_loglevel_str(clog_priority_e prio)
{
    switch (prio) {
        case CLOG_VERBOSE:       return 'V';
        case CLOG_DEBUG:         return 'D';
        case CLOG_INFO:          return 'I';
        case CLOG_WARN:          return 'W';
        case CLOG_ERROR:         return 'E';
        case CLOG_FATAL:         return 'F';

        case CLOG_UNKNOWN:
        default:                        return '?';
    }
}

static int __clog_write_entry_to_shm(int prio, const char* tag, const char* log_buf)
{
    int ret = 0;
    clog_entry_t entry;

    entry.prio = prio;
    entry.pid = getpid();
    entry.ppid = getppid();
    entry.tv_sec = time(NULL);
    entry.tv_nsec = 0;
    strncpy(entry.tag, tag, CLOG_TAG_SIZE - 1);
    entry.tag[CLOG_TAG_SIZE - 1] = '\0';
    entry.message_len = strlen(log_buf);
    strncpy(entry.message, log_buf, CLOG_BUF_SIZE - 1);
    entry.message[CLOG_BUF_SIZE - 1] = '\0';
    printf("%s", entry.message);
    if (clog_write_entry == NULL) {
        printf("clog_write_entry is NULL (uninitialized)\n");
        return -1;
    }
    ret = clog_write_entry(&entry);
    if (ret < 0) {
        printf("clog_write_entry failed\n");
        return -1;
    }
    return 0;
}

static int __clog_write_initialize(void)
{
    int32_t ret = 0;
    clog_entry_t entry;
    void *clog_so_fd = NULL;
    void *clog_write_device_init = NULL;
    const char* dlsym_err;

    clog_so_fd = dlopen(CLOG_LIB_NAME, RTLD_LAZY);
    if (clog_so_fd == NULL) {
        dlsym_err = dlerror();
        printf("dlopen failed: %s (lib: %s)\n", dlsym_err ? dlsym_err : "unknown error", CLOG_LIB_NAME);
        return -1;
    }

    dlerror();
    clog_write_device_init = dlsym(clog_so_fd, "clog_write_device_init_interface");
    if (clog_write_device_init == NULL) {
        dlsym_err = dlerror();
        printf("dlsym failed: %s (symbol: clog_write_device_init_interface)\n", dlsym_err ? dlsym_err : "unknown error");
        dlclose(clog_so_fd);
        return -1;
    }

    ret = ((int (*)(clog_entry_t*))clog_write_device_init)(&entry);
    if (ret < 0) {
        printf("clog_write_device_init failed (ret: %d)\n", ret);
        dlclose(clog_so_fd);
        return -1;
    }

    dlerror();
    clog_write_entry = dlsym(clog_so_fd, "clog_write_entry_interface");
    if (clog_write_entry == NULL) {
        dlsym_err = dlerror();
        printf("dlsym failed: %s (symbol: clog_write_entry_interface)\n", dlsym_err ? dlsym_err : "unknown error");
        dlclose(clog_so_fd);
        return -1;
    }

    return 0;
}


static void __clog_initialize_internal(void) {
    init_success = __clog_write_initialize();
    if (init_success < 0) {
        init_success = -1;
        clog_log_write_p = __clog_write_init;
    } else {
        init_success = 0;
        clog_log_write_p = __clog_write_entry_to_shm;
    }
}

static int __clog_write_init(int prio, const char* tag, const char* log_buf)
{
    pthread_once(&init_once, __clog_initialize_internal);

    if (init_success != 0) {
        printf("clog initialization failed (code: %d)\n", init_success);
        return -1;
    }

    return clog_log_write_p(prio, tag, log_buf);
}


int _clog_log_print(int prio, const char* tag, const char* fmt, ...)
{
    va_list ap;
    char log_buf[CLOG_BUF_SIZE];

    va_start(ap, fmt);
    vsnprintf(log_buf, CLOG_BUF_SIZE, fmt, ap);
    va_end(ap);

    return clog_global.clog_log_write_p(prio, tag, log_buf);
}